package pl.edu.mimuw.irs.core.model.analysis;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

import pl.edu.mimuw.irs.core.global.IDManager;
import pl.edu.mimuw.irs.core.model.analysis.dd.ActivityWrapper;
import pl.edu.mimuw.irs.core.model.design.processing.Requirement;
import pl.edu.mimuw.irs.core.model.design.processing.Resource;
import pl.edu.mimuw.irs.core.model.design.processing.Role;
import pl.edu.mimuw.irs.core.model.petrinet.Place;
import pl.edu.mimuw.irs.core.util.Position;
import pl.edu.mimuw.irs.rcp.util.Pair;

public class ResourcePlace extends Place {
	
	private static final long serialVersionUID = 1363505382837136707L;
	
	// *** Attributes
	
	private Resource resource;
	private Map<RequirementPlace, Integer> metRequirements;
	private Map<Role, Integer> rolesAvailable;
	private Map<ActivityWrapper, Map<RequirementPlace, Integer>> membership;
	public Resource getResource() {
		return resource;
	}
	public Map<RequirementPlace, Integer> getMetRequirements() {
		if (metRequirements == null) metRequirements = new LinkedHashMap<RequirementPlace, Integer>();
		if (getMembership().size() == 0) return metRequirements;
		/* 
		 * Here we know that this resource is used in some activity, so 
		 * we have to return newly created map of requirements currently met
		 * by this resource.
		 */
		return getMetRequirements(metRequirements);
	}
	public Map<Role, Integer> getRolesAvailable() {
		if (rolesAvailable == null) rolesAvailable = new LinkedHashMap<Role, Integer>();
		return rolesAvailable;
	}
	public Map<ActivityWrapper, Map<RequirementPlace, Integer>> getMembership() {
		if (membership == null) membership = 
			new LinkedHashMap<ActivityWrapper, Map<RequirementPlace, Integer>>();
		return membership;
	}

	// *** Constructors

	public ResourcePlace(IDManager idManager, Resource resource, Position position, int tokens) {
		super(idManager, idManager.getPrefixedNewId("Idle" + resource.getName() + "_"), 
				position, tokens);
		this.resource = resource;
		getRolesAvailable();
		for (Entry<Role, Integer> entry : resource.getRolesMap().entrySet()) {
			rolesAvailable.put(entry.getKey(), entry.getValue());
		}
	}

	public ResourcePlace(IDManager idManager, Resource resource, Position position) {
		this(idManager, resource, position, 0);
	}
	
	// *** Methods
	
	public Map<RequirementPlace, Integer> getMetRequirements(Map<RequirementPlace, Integer> requirements) {
		Map<RequirementPlace, Integer> newMetRequirements = 
			new LinkedHashMap<RequirementPlace, Integer>();
		for (Entry<RequirementPlace, Integer> entry : requirements.entrySet()) {
			int timesMet;
			if ((timesMet = meets(entry.getKey())) > entry.getValue()) {
				newMetRequirements.put(entry.getKey(), timesMet / entry.getValue());
			}
		}
		return newMetRequirements;
	}
	
	public Map<Requirement, Integer> getMetRequirements2(
			Map<Requirement, Integer> requirements) {
		Map<Requirement, Integer> newMetRequirements = 
			new LinkedHashMap<Requirement, Integer>();
		for (Entry<Requirement, Integer> entry : requirements.entrySet()) {
			int timesMet;
			if ((timesMet = meets(entry.getKey())) > entry.getValue()) {
				newMetRequirements.put(entry.getKey(), timesMet / entry.getValue());
			}
		}
		return newMetRequirements;
	}
	
	/*
	 * Changed implementation of resource to cope with being busy.
	 */
	private int meets(RequirementPlace requirementPlace) {
		return meets(requirementPlace.getRequirement());
	}
	
	private int meets(Requirement requirement) {
		/*
		 * Pair(REQUIRED, AVAILABLE)
		 */
		Map<Role, Pair<Integer, Integer>> map = new LinkedHashMap<Role, Pair<Integer, Integer>>();
		// XXX : Efficiency?
		for (Entry<Role, Integer> entry : requirement.getRolesMap().entrySet()) {
			map.put(entry.getKey(), new Pair<Integer, Integer>(entry.getValue(), 0));
		}
		for (Entry<Role, Integer> available : getRolesAvailable().entrySet()) {
			Pair<Integer, Integer> pair = map.get(available.getKey());
			if (pair != null) {
				pair.setValue(available.getValue());
			}
		}
		int min = Integer.MAX_VALUE;
		for (Pair<Integer, Integer> pair : map.values()) {
			/*
			 * Key should be always greater than zero
			 */
			int times = pair.getValue() / pair.getKey();
			if (times == 0) return 0;
			else if (min > times) min = times;
		}
		return min;
	}
	
	public void hire(ActivityWrapper activity, Map<RequirementPlace, Integer> requirements) {
		getRolesAvailable();
		// TODO use this instead of invoke
		//if (getMembership().size() == 0) getHire().invoke();...
		getMembership().put(activity, requirements);
		for (Entry<RequirementPlace, Integer> entry : requirements.entrySet()) {
			for (Entry<Role, Integer> role : entry.getKey().getRequirement().getRolesMap().entrySet()) {
				Integer available = rolesAvailable.get(role.getKey());
				rolesAvailable.put(role.getKey(), available - (entry.getValue() * role.getValue()));
			}
		}
	}
	
	public void fire(ActivityWrapper activity) {
		getRolesAvailable();
		Map<RequirementPlace, Integer> requirements = getMembership().get(activity);
		getMembership().remove(requirements);
		for (Entry<RequirementPlace, Integer> entry : requirements.entrySet()) {
			for (Entry<Role, Integer> role : entry.getKey().getRequirement().getRolesMap().entrySet()) {
				Integer available = rolesAvailable.get(role.getKey());
				rolesAvailable.put(role.getKey(), available + (entry.getValue() * role.getValue()));
			}
		}
	}
	
	@Override
	public String toString() {
		return "@" + getResource() + "@";
	}
}
